/**
 * Implementation of a basic (hopefully rapid) cache
 * 
 * @author remo.lemma@lu.unisi.ch
 */

package ch.unisi.inf.projects.java.uom.elements;

import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Collection;

import ch.unisi.inf.projects.java.uom.exceptions.UOMException;

public class Cache {

	/** Max size of the cache */
	private static final int MAX_CACHE_SIZE = 150; 
		
	private final List<CacheEntry> cache;
	
	private final List<Multimedia> mulCache;
	
	private boolean hasItemClasses;
	
	public Cache ()
	{
		cache = new ArrayList<CacheEntry>(1024);
		mulCache = new ArrayList<Multimedia>(1024);
		hasItemClasses = false;
	}
	
	
	public CacheEntry getEntry (final String name)
	{
		for (final CacheEntry entry : cache) {
			if (entry.getName().equals(name))
				return entry;
		}
		return null;
	}
	
	public ItemClass getItemClass (final String name)
	{
		final CacheEntry entry = getEntry(name);
		if (entry == null)
			return null;
		return entry.getItemClass();
	}
	
	public Set<ItemClass> getItemClasses ()
	{
		final Set<ItemClass> itemClasses = new HashSet<ItemClass>();
		for (final CacheEntry entry : cache)
			itemClasses.add(entry.getItemClass());
		return itemClasses;
	}

	
	public Item getItem (final String id, final ItemClass itemClass)
	{
		if (itemClass == null)
			return null;
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null)
			return null;
		return entry.getItem(id);
	}

	public Set<Item> getItemsFromItemClass (final ItemClass itemClass)
	{
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null)
			return null;
		return new HashSet<Item>(entry.getItems()); 
	}
	
	public Set<Item> getItems()
	{
		final Set<Item> items = new HashSet<Item>();
		for (final CacheEntry entry : cache)
			items.addAll(entry.getItems());
		return items;
	}
	
	public void putItem (final Item item)
	{
		if (cache.size() >= MAX_CACHE_SIZE)
			cache.remove(0);
		try {
			if (item.isNew())
				return;
		}
		catch (final UOMException uome)
		{
			return;
		}
		final ItemClass itemClass = item.getItemClass();
		final CacheEntry entry = getEntry(itemClass.getName());
		final Set<Item> items = new HashSet<Item>();
		items.add(item);
		if (entry == null)
			cache.add(new CacheEntry(itemClass.getName(), itemClass, items));
		else
			entry.putItem(item);
	}
	
	public void putItemClass (final ItemClass itemClass)
	{
		if (cache.size() >= MAX_CACHE_SIZE)
			cache.remove(0);
		if (itemClass == null || itemClass.getName().toUpperCase().equals("ALL CATEGORIES"))
			return;
		else if (getEntry(itemClass.getName()) == null)
			cache.add(new CacheEntry(itemClass.getName(), itemClass, new HashSet<Item>()));
	}
	
	private void removeItemClass (final ItemClass itemClass)
	{
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null)
			return;
		cache.remove(entry);
	}
	
	private void removeItem (final Item item)
	{
		final String id = item.getId();
		final ItemClass itemClass = item.getItemClass();
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null)
			return;
		entry.removeItem(id);
	}
	
	public void removeElement (final Element e)
	{
		if (e instanceof Item)
			removeItem((Item) e);
		else if (e instanceof ItemClass)
			removeItemClass((ItemClass) e);
		else
			throw new IllegalArgumentException("The argument " + e + " is not from a valid class");
	}
	
	public void saveElement (final Element e)
	{
		if (e instanceof Item)
			putItem((Item) e);
		else if (e instanceof ItemClass) {
			putItemClass((ItemClass) e);
			updateItems((ItemClass) e);
		}
		else
			throw new IllegalArgumentException("The argument " + e + " is not from a valid class");
	}
	
	private void updateItems (final ItemClass itemClass)
	{
		final CacheEntry entry = getEntry(itemClass.getName());
		for (final String field : itemClass.getFields().keySet()) {
			for (final Item item : entry.getItems())
				item.addField(field, "");
		}
	}
	
	public boolean alreadySeen (final ItemClass itemClass)
	{
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null) {
			this.putItemClass(itemClass);
			return false;
		}
		return entry.isSynced();
	}
	
	public void setSeen (final ItemClass itemClass)
	{
		final CacheEntry entry = getEntry(itemClass.getName());
		if (entry == null)
			return;
		entry.setSync(true);
	}
	
	public boolean hasItemClasses ()
	{
		return hasItemClasses;
	}
	
	public void setHasItemClasses (final boolean hasItemClasses)
	{
		this.hasItemClasses = hasItemClasses;
	}
	
	public boolean hasAll ()
	{
		if (!hasItemClasses)
			return false;
		for (final CacheEntry entry : cache) {
			if (!entry.isSynced())
				return false;
		}
		return true;
	}
	
	public Multimedia getMultimedia (final String id)
	{
		for (final Multimedia m : mulCache) {
			if (m.getId().equals(id))
				return m;
		}
		return null;
	}
	
	public void addMultimedia (final Multimedia multimedia)
	{
		if (mulCache.size() >= MAX_CACHE_SIZE)
			mulCache.remove(0);
		if (multimedia == null)
			return;
		final Multimedia m = getMultimedia(multimedia.getId());
		if (m == null)
			mulCache.add(multimedia);
	}
	
	public void removeMultimedia (final Multimedia multimedia)
	{
		final Multimedia m = getMultimedia(multimedia.getId());
		if (m != null)
			mulCache.remove(multimedia);
	}
	
	public void clear ()
	{
		cache.clear();
		mulCache.clear();
		this.hasItemClasses = false;
	}
}

class CacheEntry {
	
	private final String itemClassName;
	
	private final ItemClass itemClass;
	
	private final Map<String, Item> items;
	
	private boolean synced;
	
	public CacheEntry (final String itemClassName, final ItemClass itemClass, final Set<Item> items)
	{
		this.itemClassName = itemClassName;
		this.synced = false;
		this.itemClass = itemClass;
		this.items = new HashMap<String,Item>(items.size());
		for (final Item item : items)
			putItem(item);
	}
	
	public String getName ()
	{
		return itemClassName;
	}
	
	public ItemClass getItemClass ()
	{
		return itemClass;
	}
	
	public Collection<Item> getItems ()
	{
		return items.values();
	}
	
	public Item getItem (final String id)
	{
		return items.get(id);
	}
	
	public boolean isSynced ()
	{
		return this.synced;
	}
	
	public void setSync (final boolean synced)
	{
		this.synced = synced;
	}
	
	public void putItem (final Item item)
	{
		items.put(item.getId(), item);
	}
	
	public void removeItem (final String id)
	{
		items.remove(id);
	}
}
